package priv.utrix.micro.annotation.handle;

import cn.hutool.core.util.StrUtil;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.stereotype.Component;
import priv.utrix.micro.annotation.Idempotent;
import priv.utrix.micro.dto.SecurityUserDto;
import priv.utrix.micro.utils.SecurityUtil;

import java.lang.reflect.Method;
import java.util.Optional;
import java.util.concurrent.locks.Lock;

/**
 * 幂等切面
 *
 * @author utrix
 * @date 2020/11/28
 */
@Aspect
@Component
@RequiredArgsConstructor
public class IdempotentAspect {

    final Redisson redisson;

    //切点(切入所有注解为Idempotent的方法)
    @Pointcut("@annotation(priv.utrix.micro.annotation.Idempotent)")
    public void idempotentAnnotation() {}

    @Around("idempotentAnnotation()")
    public void ensureIdempotence(ProceedingJoinPoint point) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();
        if (!method.isAnnotationPresent(Idempotent.class)) {
            point.proceed();
            return;
        }

        SecurityUserDto user = SecurityUtil.getUser();
        Preconditions.checkNotNull(user,"User is not login");
        // 获取切点方法字符串, 类名.方法名()
        final String aspectPart = point.getSignature().toShortString();
        String redisKey = Optional.ofNullable(point.getArgs()).map(args -> {
            String argJoin = Joiner.on(",").join(args);
            return StrUtil.strBuilder(argJoin).append(user.getUsername()).append(aspectPart).toString();
        }).orElse(aspectPart);

        RLock redissonLock = null;
        try {
            redissonLock = redisson.getLock(redisKey);
            // 设置超时时间，避免死锁问题
            Idempotent annotation = method.getAnnotation(Idempotent.class);
            redissonLock.lock(annotation.expireTime(), annotation.timeUnit());
            point.proceed();
        } finally {
            Optional.ofNullable(redissonLock).ifPresent(Lock::unlock);
        }
    }

}
